﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Forms;
using Newtonsoft.Json;
using UpLoadApi.Models;

namespace WFSendFile
{
    public partial class Form1 : Form
    {
        CancellationTokenSource cancelSource; //取消异步任务标记
        public Form1()
        {           
            InitializeComponent();
            Tb_Url.Text = "http://localhost:8043/api/Upload";
            TB_SendDataUrl.Text = "http://localhost:52211/api/SendData/SendListJsontData";
            TB_MF_Url.Text = "http://localhost:52211/api/UploadMultiPart/UploadFile";
        }

        private void Btn_BrowserFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Title = "请选择需要发送的文件:";
            if (DialogResult.OK == fileDialog.ShowDialog())
            {
                Tb_FileName.Text = fileDialog.FileName;
            }

        }

        private async void Btn_Send_Click(object sender, EventArgs e)
        {
            TB_Status.Text = "Sending...";
            cancelSource = new CancellationTokenSource();//CancellationTokenSource每次用时需要重新new

            if (string.IsNullOrEmpty(Tb_FileName.Text))
                return;

            string filePath = Tb_FileName.Text;
            string fileNameWithExt = Path.GetFileName(filePath);

            if (string.IsNullOrEmpty(Tb_Url.Text))
                return;
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(Tb_Url.Text);
            request.ContentType = "multipart/form-data";

            //对含中文的header属性值进行编码传输
            byte[] fileNameBytes = Encoding.UTF8.GetBytes(fileNameWithExt);//Encoding.UTF8.GetString(fileNameBytes);//解码
            string fileNameEncoderStr = BitConverter.ToString(fileNameBytes);

            request.Headers.Add("filename", fileNameEncoderStr);//如果文件名包含中文，需要编码
            request.Timeout = 600000;
            request.AllowWriteStreamBuffering = false;//取消缓存，否则内存会溢出
            
            request.Method = "post";
            /*
            string strBoundary = "0123456789";// + DateTime.Now.Ticks.ToString("x");
            byte[] boundaryBytes = Encoding.ASCII.GetBytes(strBoundary);//"/r/n--" + strBoundary + "/r/n");
            */
           
            //要上传的文件
            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            
            request.ContentLength = fs.Length;/*+ boundaryBytes.Length*/ //指定文件长度

            /*//将额外信息以流的方式写入，传到服务端（实际可以以更优雅的方式，放入到头立面）
            Stream postStream = request.GetRequestStream();
            //发送请求头部消息  
            postStream.Write(boundaryBytes, 0, boundaryBytes.Length);
            */

            

            try
            {
                const long buffSize4M = 1024*1024*4;//这个似乎影响了传输速度
                long buffSize = 0;
                buffSize = checked(Math.Min(fs.Length, buffSize4M));//最小以文件为单位
                byte[] buff = new byte[buffSize];
                int bytesRead = 0;//一次读取的字节数

                #region progressBar Info
                this.ProgBarUpload.Minimum = 0;
                this.ProgBarUpload.Maximum = (int)(Math.Round((double)fs.Length/ buffSize));// !存在转换隐患
                this.ProgBarUpload.Value = 0;

                DateTime startTime = DateTime.Now;

                #endregion

                var requestStream = request.GetRequestStream();
                do
                {
                    Task<int> taskRead = fs.ReadAsync(buff, 0, buff.Length,cancelSource.Token);

                    TimeSpan span = DateTime.Now - startTime;
                    double spanToSecond = span.TotalSeconds;
                    this.Lb_ConsumeTime.Text = "已用时： " + spanToSecond.ToString("F2") + " 秒";

                    bytesRead = await taskRead;
                    if (bytesRead == 0)//读取结束
                        break;
                  
                    if (spanToSecond>0.001) 
                    {
                        this.Lb_SpeedText.Text = (bytesRead/ (spanToSecond * 1024*1024)).ToString("0.00")+"MB/秒";
                    }
                   
                    Task taskWrite = requestStream.WriteAsync(buff, 0, bytesRead);

                    if(this.ProgBarUpload.Value< this.ProgBarUpload.Maximum)
                        this.ProgBarUpload.Value++;//更新进度条
                    //显示百分比
                    this.Lb_ProgressPercent.Text = ((double)this.ProgBarUpload.Value*100 / this.ProgBarUpload.Maximum).ToString("0.0") + "%" ;

                    await taskWrite;                   
                } while (bytesRead != 0);//bytesRead == 0 表示读到了流的结尾

                //得到服务端方法响应
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                StreamReader reader = new StreamReader(response.GetResponseStream());//读取响应流
                string responseContent;
                responseContent = reader.ReadToEnd();

                TB_Status.Text = responseContent;

                requestStream.Close();
                reader.Close();
                response.Close();
                fs.Close();
                //postStream.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void Btn_CancelSend_Click(object sender, EventArgs e)
        {
            if(cancelSource!=null)
                cancelSource.Cancel();
        }

        private async void Btn_SendData_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(TB_SendDataUrl.Text))
                return;
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(TB_SendDataUrl.Text);
            //request.ContentType = "application/x-www-form-urlencoded";//post发送数据必须使用这个标头才能被webapi [frombody]接收
            request.ContentType = "application/json";   //如果用了newtonsoft json 转换数据，那么必须用此标头，否则服务器将无法解析数据
            request.Method = "post";

            //发送的数据
            List<Person> listP = new List<Person>();
            Person p1 = new Person { ID = 1, Name = "张三", Age = 27 };
            Person p2 = new Person { ID = 2, Name = "李四", Age = 25 };
            Person p3 = new Person { ID = 3, Name = "王五", Age = 28 };
            listP.Add(p1); listP.Add(p2); listP.Add(p3);


            byte[] byteArray;
            Person p = new Person { ID = 1, Name = "张三", Age = 27 };
            //byteArray = Encoding.UTF8.GetBytes(ParseToString(p));
            string data = await JsonConvert.SerializeObjectAsync(listP);//直接序列化比上一行代码 更方便
            byteArray = Encoding.UTF8.GetBytes(data);//对数据进行转码，可以将中文数据进行传输
            request.ContentLength = byteArray.Length;

            Stream reqStream = await request.GetRequestStreamAsync();
            reqStream.Write(byteArray,0,byteArray.Length);
            reqStream.Close();

            string responseFromServer = string.Empty;
            WebResponse response = await request.GetResponseAsync();
            using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
            {
                responseFromServer = reader.ReadToEnd();
            }
            if(string.IsNullOrEmpty(responseFromServer))
            {
                TB_SendResponseData.Text = "NULL";
            }
            else
            {
                TB_SendResponseData.Text = responseFromServer;
                List<Person> resData = JsonConvert.DeserializeObject<List<Person>>(responseFromServer);
                
            }
            response.Close();
        }
        //使用json替换这个方法
        private static string ParseToString<T>(T obj)
        {
            StringBuilder sb = new StringBuilder();
            var props = typeof(T).GetProperties();
            foreach (PropertyInfo p in props)
            {
                var propVal = p.GetValue(obj, null);
                if (propVal == null)
                    continue;
                sb.Append("&");
                sb.Append(p.Name);
                sb.Append("=");
                sb.Append(HttpUtility.UrlEncode(propVal.ToString()));
            }
            return sb.ToString().TrimStart('&');
        }

        //发送multipart-formdata
        private async void Btn_MF_Send_Click(object sender, EventArgs e)
        {
            try
            {
                string boundary = "----------" + DateTime.Now.Ticks.ToString("x");//元素分割标记 
                StringBuilder sb = new StringBuilder();
                sb.Append("--" + boundary);
                //这是第一条拼接的表单数据title：Lee
                sb.Append("\r\n"); 
                sb.Append("Content-Disposition: form-data; name=\"title\"");//新起一行前面必须"\r\n"
                sb.Append("\r\n");
                sb.Append("\r\n");
                sb.Append("Lee");//value前面必须有2个换行  "\r\n"
                sb.Append("\r\n");
                sb.Append("--" + boundary);
                //这是第二条拼接的表单数据Name：张三
                sb.Append("\r\n");
                sb.Append("Content-Disposition: form-data; name=\"Name\"");
                sb.Append("\r\n");
                sb.Append("\r\n");
                sb.Append("张三");//value前面必须有2个换行  "\r\n"
                sb.Append("\r\n");
                sb.Append("--" + boundary);//boundary分隔符前面只需要一个"\r\n"
                //下面是文件流数据
                sb.Append("\r\n");
                sb.Append("Content-Disposition: form-data; name=\"11\"; filename=\"11.docx" + "\"");
                sb.Append("\r\n");
                sb.Append("Content-Type: application/octet-stream ");
                sb.Append("\r\n");
                sb.Append("\r\n");//value前面必须有2个换行 "\r\n" 
                //这一行后面开始写文件内容

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(TB_MF_Url.Text);
                request.ContentType = "multipart/form-data;boundary=" + boundary;//其它地方的boundary比这里在前面多--
                request.Method = "post";
                //文件流
                FileStream fs = new FileStream(@"E:\UpLoad\11.docx", FileMode.OpenOrCreate, FileAccess.Read);
                //post流的开始部份
                byte[] postStartBytes = Encoding.UTF8.GetBytes(sb.ToString());
                //post流的尾巴部份
                byte[] postEndBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary+"--\r\n");//特别注意结尾符号也有--，否则将无法正确在服务端识别到结束符
                //post消息主体的长度
                request.ContentLength = postStartBytes.Length + fs.Length + postEndBytes.Length;
                //获取请求流
                Stream reqStream = await request.GetRequestStreamAsync();
                //写入开始部分
                await reqStream.WriteAsync(postStartBytes, 0, postStartBytes.Length);
                //文件流写入的缓冲区
                byte[] buff = new byte[checked(Math.Min(1024 * 1024 * 4, fs.Length))];
                //写入文件流
                int bytesRead = 0;
                while ((bytesRead=await fs.ReadAsync(buff,0,buff.Length))!=0)
                {
                    await reqStream.WriteAsync(buff, 0, bytesRead);
                }
                //写入消息主体的尾巴部分
                await reqStream.WriteAsync(postEndBytes, 0, postEndBytes.Length);

                reqStream.Close();
                fs.Close();
                //请求响应
                WebResponse response = await request.GetResponseAsync();
                Stream resStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(resStream);
                string res = await reader.ReadToEndAsync();

                TB_MF_RES.Text = res;
                reader.Close();
                resStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }           
        }
    }
}
